#include <mc/world.h>
#include <mc/types.h>
#include <mc/blocks.h>
#include <mc/chunk.h>
#include <mc/chunkid.h>
#include <mc/loader.h>
#include <iostream>
#include <list>
#include <vector>

namespace mc
{
//! @brief 私有构造
World::World() : 
    _seed(0),
    _radius(2),
    _chunkMap{},
    _chunks{},
    _chunkRadius(2),
    _sky(nullptr),
    _crosshair(nullptr) 
{
}

//! @brief 析构
World::~World()
{
#ifdef DEBUG
    std::cout << "free world\n";
#endif
    this->release();
}

//! @brief 获取坐标 [x, y, z] 处的方块类型
const BlockType& World::block(int64_t x, int64_t y, int64_t z) const
{
    ChunkID id = Chunk::getChunkID(x, z);
    int biasX = x - id.coord.x * CHUNK_SZIE;
    int biasZ = z - id.coord.y * CHUNK_SZIE;

    return this->_chunkMap.find(id)->second.chunk->blockData()[biasX][util::ROUND(y)][biasZ];
}

//! @brief 在坐标 [x, y, z] 处放置方块
void World::placeBlock(int64_t x, int64_t y, int64_t z, BlockType block, bool update)
{
    ChunkID id = Chunk::getChunkID(x, z);
    int biasX = x - id.coord.x * CHUNK_SZIE;
    int biasZ = z - id.coord.y * CHUNK_SZIE;
    Chunk* chunk = this->_chunkMap.find(id)->second.chunk;

    chunk->placeBlock(biasX, y, biasZ, block, update);
}

//! @brief 移除坐标 pos 处的方块，并且返回原方块类型
BlockType World::removeBlock(int64_t x, int64_t y, int64_t z, bool update) 
{ 
    ChunkID id = Chunk::getChunkID(x, z);
    int biasX = x - id.coord.x * CHUNK_SZIE;
    int biasZ = z - id.coord.y * CHUNK_SZIE;
    Chunk* chunk = this->_chunkMap.find(id)->second.chunk;

    return chunk->removeBlock(biasX, y, biasZ, update);
}

//! @brief 绘制世界
void World::draw(const glm::mat4& view)
{
    // 渲染方块
    mc::Block::prepareRender(view);
    for (Chunk* chunk : this->_chunks)
        chunk->draw();

    // 渲染天空
    glm::mat4 skyView = glm::mat4(glm::mat3(view));
    mc::Sky::prepareRender(skyView);
    this->_sky->draw();

    // 渲染准心
    mc::Crosshair::prepareRender();
    this->_crosshair->draw();
}

//! @brief 初始化世界资源
void World::initial(const glm::mat4& projection)
{
    // 加载资源：VAO、VBO、着色器等
    Block::createShader(projection);
    Sky::createShader(projection);
    Crosshair::createShader();

    BlockFactory::loadAllBlockTextures();

    // 初始化天空
    this->_sky = new Sky({
        "skybox/right.png",
        "skybox/left.png",
        "skybox/top.png",
        "skybox/bottom.png",
        "skybox/front.png",
        "skybox/back.png"
    });

    // 初始化准心
    this->_crosshair = new Crosshair("crosshair.png");
}

//! @brief 更新区块
void World::updateChunks(const ChunkID& oldChunkId, const ChunkID& newChunkId)
{
    int deltaX = newChunkId.coord.x - oldChunkId.coord.x;
    int deltaZ = newChunkId.coord.y - oldChunkId.coord.y;

    std::vector<ChunkID> addChunks;
    std::vector<ChunkID> lossChunks;

    if (deltaX == 0 || deltaZ == 0)
    {
        unsigned int chunkCnt = 2 * this->_radius + 1;
        addChunks.assign(chunkCnt, newChunkId);
        lossChunks.assign(chunkCnt, oldChunkId);

        if (deltaX == 0)
        {
            for (unsigned int i = 0; i < chunkCnt; ++i)
            {
                addChunks[i].coord.x += (-1 * this->_radius + i);
                lossChunks[i].coord.x += (-1 * this->_radius + i);

                addChunks[i].coord.y += (this->_radius * deltaZ);
                lossChunks[i].coord.y -= (this->_radius * deltaZ);
            }
        }
        else if (deltaZ == 0)
        {
            for (unsigned int i = 0; i < chunkCnt; ++i)
            {
                addChunks[i].coord.y += (-1 * this->_radius + i);
                lossChunks[i].coord.y += (-1 * this->_radius + i);

                addChunks[i].coord.x += (this->_radius * deltaX);
                lossChunks[i].coord.x -= (this->_radius * deltaX);
            }
        }
    }
    else
    {
        unsigned int chunkCnt = 4 * this->_radius + 1;
        addChunks.assign(chunkCnt, newChunkId);
        lossChunks.assign(chunkCnt, oldChunkId);

        for (unsigned int i = 0; i < 2 * this->_radius; ++i)
        {
            addChunks[i].coord.x += (deltaX * this->_radius - deltaX * (i + 1));
            addChunks[i].coord.y += (deltaZ * this->_radius);

            addChunks[chunkCnt - 1 - i].coord.x += (deltaX * this->_radius);
            addChunks[chunkCnt - 1 - i].coord.y += (deltaZ * this->_radius - deltaZ * (i + 1));
        }
        addChunks[2 * this->_radius].coord.x += (deltaX * this->_radius);
        addChunks[2 * this->_radius].coord.y += (deltaZ * this->_radius);

        for (unsigned int i = 0; i < 2 * this->_radius; ++i)
        {
            lossChunks[i].coord.x += (-1 * deltaX * this->_radius + deltaX * (i + 1));
            lossChunks[i].coord.y += (-1 * deltaZ * this->_radius);

            lossChunks[chunkCnt - 1 - i].coord.x += (-1 * deltaX * this->_radius);
            lossChunks[chunkCnt - 1 - i].coord.y += (-1 * deltaZ * this->_radius + deltaZ * (i + 1));
        }
        lossChunks[2 * this->_radius].coord.x += (-1 * deltaX * this->_radius);
        lossChunks[2 * this->_radius].coord.y += (-1 * deltaZ * this->_radius);
    }

    static Loader* loader = Loader::instance();
    // 保存区块
    for (auto iter = lossChunks.begin(); iter != lossChunks.end(); ++iter)
        loader->saveChunk(*iter);
    
    // 加载区块
    for (auto iter = addChunks.begin(); iter != addChunks.end(); ++iter)
        loader->loadChunk(*iter);

}

//! @brief 释放资源
void World::release()
{
    // 释放天空
    delete this->_sky;

    // 释放准心
    delete this->_crosshair;

    // 释放已经存在的区块
    for (Chunk* chunk : this->_chunks)
        delete chunk;
}

}